﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using EFDataModel;
using EFDataModel.Enums;

namespace ContactMvc4TB.Helpers.DynamicDataType
{
    /// <summary>
    /// Servervalidierung der Dynmaischen Daten nach dem Postback, da auf die JavaScript Validierung nicht immer zurück gegriffen werden kann und auch nicht immer verlass ist.
    /// Validierung der DynamicDaten nach einem Postback, es wird automatisch ein Fehlder dem aktuellen ModelContext hinzugefügt, damit dieser nicht mehr Valid ist.
    /// Außerdem wird eine weitere Liste dem Tempdata hinzugefügt, die für die Fehler zuständig ist.
    /// </summary>
    public class DynamicDataValidation
    {
        #region Member
        private SortedList<int, DynamicDataValidationError> _validationErrorList;
        #endregion

        #region Properties
        /// <summary>
        /// Der Aktuelle controllerkontext.
        /// </summary>
        private ControllerContext CurrentControllerContext { get; set; }

        /// <summary>
        /// Der aktuelle ModelBinderContext
        /// </summary>
        private ModelBindingContext CurrentModelBindingContext { get; set; }

        /// <summary>
        /// Liste mit den gefundenen Fehlern, jede Überprüfung mit ValidateValue fügt einen Fehler zur Liste hinzu, 
        /// wenn der Wert nicht Valide war, damit über TempData später beim Erstellen der Controls die passenden Fehler angezeigt werden können.
        /// </summary>
        private SortedList<int, DynamicDataValidationError> ValidationErrorList
        {
            get { return _validationErrorList; }
        }
        #endregion

        #region Konstruktor
        /// <summary>
        /// Initialisieren der DynamicDadaValidation.
        /// </summary>
        /// <param name="controllerContext">Der aktuelle ControllerContext</param>
        /// <param name="bindingContext">der aktuelle BindingContext</param>
        public DynamicDataValidation(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            CurrentControllerContext = controllerContext;
            CurrentModelBindingContext = bindingContext;
            _validationErrorList = new SortedList<int, DynamicDataValidationError>();
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Überprüft den übergebenen Wert, ob dieser Valide ist für den zugehörigen dynmaischen Datentyp
        /// </summary>
        /// <param name="value">der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der DynamicDataType für den Value der überprüft werden soll</param>
        /// <returns>TRUE->Wert ist Valide | FALSE->Eingabewert stimmt nicht mit dem Typ überein</returns>
        public bool ValidateValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Prüfen um welchen Datentyp es sich handelt, der überprüft werden soll
            switch (dynamicDataType.DynamicDataType)
            {
                //Alle Strings werden gleich behandelt, den Email unterscheidet sich nur das hier prinzipiell ein Regex Ausdruck hinterlegt ist.
                case EFDynamicDataTypes.Textblock:
                case EFDynamicDataTypes.EMail:
                case EFDynamicDataTypes.String:
                    return ValidateStringValue(value, dynamicDataType);

                case EFDynamicDataTypes.Datetime:
                    return ValidateDateTimeValue(value, dynamicDataType);

                case EFDynamicDataTypes.Boolean:
                    //hier kann keine Validierung vorgenommen werden, da es nur true/false sein kann.
                    return true;

                case EFDynamicDataTypes.Integer:
                    return ValidateIntegerValue(value, dynamicDataType);

                case EFDynamicDataTypes.Decimal:
                    return ValidateDecimalValue(value, dynamicDataType);

                case EFDynamicDataTypes.List:
                    return ValidateListValue(value, dynamicDataType);
            }

            return false;
        }

        /// <summary>
        /// Gint zurück ob ValidationErrors in der Liste mit den Validierungsfehlern enthalten sind
        /// </summary>
        /// <returns>TRUE->Es gab Validationfehler | FALSE->Es gab KEINE Validierungsfehler</returns>
        public bool HasValidationErrors()
        {
            //Wenn Einträge enthalten sind, dann sind Fehler aufgetreten
            if (ValidationErrorList.Count > 0)
            {
                return true;
            }

            //Es sind keine Fehler aufgetreten
            return false;
        }
        #endregion

        #region Private Functions
        /// <summary>
        /// Prüft ob der Listeneintrag auch richtig ausgewählt wurde. (Wenn kein Wert ausgewählt wurde wird ein Leerer String übergeben, sonst eine Id)
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool ValidateListValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Den übergebenen Wert als String behandeln
            string currentValue = value as string;

            //der Wert ist erforderlich, dann weitere Prüfungen vornehmen
            if (dynamicDataType.IsRequired)
            {
                //Wenn das Feld erforderlich ist, aber kein Wert angegeben wurde, dann einen Fehler zurückgeben
                if (string.IsNullOrEmpty(currentValue))
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(currentValue, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.ValueRequiredRESX, dynamicDataType.Name)));
                    return false;
                }

                //Prüfen ob es sich um einen Validen String handelt
                return CheckString(currentValue, dynamicDataType);
            }
            else
            {
                //Die Eingabe ist nicht zwingend erforderlich, daher den String nur prüfen wenn auch einer angegeben wurde.
                if (!string.IsNullOrEmpty(currentValue))
                {
                    //Prüfen des Strings, nur wenn auch einer angegeben wurde
                    return CheckString(currentValue, dynamicDataType);
                }
            }

            //Alle eingaben sind valide
            return true; 
        }

        /// <summary>
        /// Prüft ob es sich um einen Gültigen String handelt, anhand der Daten die im DynamicDataType hinterlegt sind.
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool ValidateStringValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Den übergebenen Wert als String behandeln
            string currentValue = value as string;

            //der Wert ist erforderlich, dann weitere Prüfungen vornehmen
            if (dynamicDataType.IsRequired)
            {
                //Wenn das Feld erforderlich ist, aber kein Wert ausgewählt wurde, einen Fehler zurück geben
                if (string.IsNullOrEmpty(currentValue))
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(currentValue, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.ValueRequiredRESX, dynamicDataType.Name)));
                    return false;
                }
            }

            //Alle eingaben sind valide
            return true;
        }

        /// <summary>
        /// Überprüft alle Stringrelevanten Einstellungen
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool CheckString(string value, EFDynamicDataType dynamicDataType)
        {
            //Prüfen ob ein Regexeintrag angegeben ist und ob dieser überprüft werden soll
            if (!string.IsNullOrEmpty(dynamicDataType.RegExString))
            {
                if (!Regex.IsMatch(value, dynamicDataType.RegExString))
                {
                    //Wenn es sich um einen Fehler handelt, diesen der Auflistung mit den Eingabefeldfehlern hinzufügen
                    AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,GlobalResources.SystemMessages.WrongValueFormatRESX));
                    return false;
                }
            }

            //Reihenfolge ist wichtig, als erstes prüfen ob beide Werte angegeben werden sollen.
            //Der String darf maximal xx lang sein und minimal xx kurz sein
            if (dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value.Length > dynamicDataType.MaxLength || value.Length < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.StringMinMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der String darf maximal xxx Lang sein
            if (dynamicDataType.MaxLength.HasValue && !dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value.Length > dynamicDataType.MaxLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.StringMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der String muss mindestens xx kurz sein
            if (!dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value.Length < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.StringMinLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength)));
                    return false;
                }
            }

            //Alle Eingaben sind i.o. dann kann True zurück gegeben werden.
            return true;
        }

        /// <summary>
        /// Prüft ob es sich um ein Gültiges Datum handelt, anhand der Daten die im DynamicDataType hinterlegt sind.
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool ValidateDateTimeValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Wenn der Wert gefordert wird.
            if (dynamicDataType.IsRequired)
            {
                try
                {
                    //Prüfen ob ein Wert angegeben wurde, wenn nicht, dann die Required Meldung ausgeben als Fehler
                    if (string.IsNullOrEmpty((string)value))
                    {
                        AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,GlobalResources.SystemMessages.ValueRequiredRESX));
                        return false;
                    }

                    //Den Wert in ein gültiges Datum umwandeln
                    DateTime currentTime = DateTime.Parse(value);
                }
                catch (Exception)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.DateTimeNotValidRESX, DateTime.Now.ToString(WebConstants.InputDateTimeFormat))));
                    return false;
                }
            }
            else
            {
                //Prüfen ob ein Wert übergeben wurde, wenn ja auch diesen wieder versuchen in ein gültiges Datum umzuwandeln
                if (!string.IsNullOrEmpty((string)value))
                {
                    try
                    {
                        //Den Wert in ein gültiges Datum umwandeln
                        DateTime currentTime = DateTime.Parse(value);
                    }
                    catch (Exception)
                    {
                        AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,
                                                      string.Format(GlobalResources.SystemMessages.DateTimeNotValidRESX, DateTime.Now.ToString(WebConstants.InputDateTimeFormat))));
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Prüft ob es sich um einen Gültigen Integer Wert handelt, anhand der Daten die im DynamicDataType hinterlegt sind.
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool ValidateIntegerValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Ist die Werteeingabe erforderlich
            if (dynamicDataType.IsRequired)
            {
                try
                {
                    //Prüfen ob überhaupt ein Wert angegeben wurde, wenn nicht die Required Meldung ausgeben
                    if (string.IsNullOrEmpty((string)value))
                    {
                        AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,GlobalResources.SystemMessages.ValueRequiredRESX));
                        return false;
                    }

                    //Prüfen ob es sich um einen Validen Integer Wert handelt.
                    int currentValue = int.Parse(value);
                    return CheckInteger(currentValue, dynamicDataType);
                }
                catch (Exception)
                {
                    //der Wert konnte nicht in einen Integer Wert umgewandelt werden
                    AddValueToValidationErrorList(new DynamicDataValidationError((string)value, dynamicDataType.ID,
                                                                                 string.Format(GlobalResources.SystemMessages.WrongIntegerFormatRESX, dynamicDataType.Name)));
                    return false;
                }
            }
            else
            {
                //Prüfen ob ein Wert hinterlegt wurde, auch wenn die Eingabe nicht erforderlich ist
                if (!string.IsNullOrEmpty(value))
                {
                    try
                    {
                        //Prüfen ob es sich um einen Validen Integer Wert handelt.
                        int currentValue = int.Parse(value);
                        return CheckInteger(currentValue, dynamicDataType);
                    }
                    catch (Exception)
                    {
                        //der Wert konnte nicht in einen Integer Wert umgewandelt werden
                        AddValueToValidationErrorList(new DynamicDataValidationError((string)value, dynamicDataType.ID,
                                                                                     string.Format(GlobalResources.SystemMessages.WrongIntegerFormatRESX, dynamicDataType.Name)));
                        return false;
                    }
                }
            }

            //Alle Werte i.o.
            return true;
        }

        /// <summary>
        /// Prüfen ob alle Einstellungen für einen Integer und Decimal Wert eingehalten wurden
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">Der Dynamische Datentyp der die Regeln beinhaltet</param>
        /// <returns>TRUE->Eingabe richtig | FALSE->Fehler bei der Eingabe</returns>
        private bool CheckInteger(int value, EFDynamicDataType dynamicDataType)
        {
            //Prüfen ob ein Regexeintrag angegeben ist und ob dieser überprüft werden soll auch bei Zahlen kann evtl. ein bestimmtes Format gefordert sein
            if (!string.IsNullOrEmpty(dynamicDataType.RegExString))
            {
                if (!Regex.IsMatch(value.ToString(), dynamicDataType.RegExString))
                {
                    //Wenn es sich um einen Fehler handelt, diesen der Auflistung mit den Eingabefeldfehlern hinzufügen
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,GlobalResources.SystemMessages.WrongValueFormatRESX));
                    return false;
                }
            }

            //Reihenfolge ist wichtig, als erstes prüfen ob beide Werte angegeben werden sollen.
            //Der Integer darf maximal xx Groß sein und minimal xx Klein sein
            if (dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob die Zahl größer ist wie gefordert bzw. kleiner ist wie gefordert
                if (value > dynamicDataType.MaxLength || value < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMinMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der Integer darf maximal xxx groß sein
            if (dynamicDataType.MaxLength.HasValue && !dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value > dynamicDataType.MaxLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der Integer muss mindestens xx lang sein
            if (!dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMinLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength)));
                    return false;
                }
            }

            //Alle Eingaben sind i.o. dann kann True zurück gegeben werden.
            return true;
        }

        /// <summary>
        /// Prüft ob es sich um einen gültigen Decimal Wert handelt, anhand der Daten die im DynamicDataType hinterlegt sind.
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">der zugehörige DynamicDataType in dem die Daten stehen die mit dem String übereinstimmen sollen wie z.B. RegexValue</param>
        /// <returns>TRUE->Valide Eingabe | FALSE->kein Valider Eingabewert</returns>
        private bool ValidateDecimalValue(dynamic value, EFDynamicDataType dynamicDataType)
        {
            //Ist die Werteeingabe erforderlich
            if (dynamicDataType.IsRequired)
            {
                try
                {
                    //Prüfen ob überhaupt ein Wert angegeben wurde, wenn nicht die Required Meldung ausgeben
                    if (string.IsNullOrEmpty((string)value))
                    {
                        AddValueToValidationErrorList(new DynamicDataValidationError(value, dynamicDataType.ID,GlobalResources.SystemMessages.ValueRequiredRESX));
                        return false;
                    }

                    //Prüfen ob es sich um einen Validen Integer Wert handelt.
                    decimal currentValue = decimal.Parse(value);
                    return CheckDecimal(currentValue, dynamicDataType);
                }
                catch (Exception)
                {
                    //der Wert konnte nicht in einen Decimal Wert umgewandelt werden
                    AddValueToValidationErrorList(new DynamicDataValidationError((string)value, dynamicDataType.ID,
                                                                                 string.Format(GlobalResources.SystemMessages.WrongDecimalFormatRESX, dynamicDataType.Name)));
                    return false;
                }
            }
            else
            {
                //Prüfen ob ein Wert hinterlegt wurde, auch wenn die Eingabe nicht erforderlich ist
                if (!string.IsNullOrEmpty(value))
                {
                    try
                    {
                        //Prüfen ob es sich um einen Validen Integer Wert handelt.
                        decimal currentValue = decimal.Parse(value);
                        return CheckDecimal(currentValue, dynamicDataType);
                    }
                    catch (Exception)
                    {
                        //der Wert konnte nicht in einen Integer Wert umgewandelt werden
                        AddValueToValidationErrorList(new DynamicDataValidationError((string)value, dynamicDataType.ID,
                                                                                     string.Format(GlobalResources.SystemMessages.WrongDecimalFormatRESX, dynamicDataType.Name)));
                        return false;
                    }
                }
            }

            //Alle Werte i.o.
            return true;
        }

        /// <summary>
        /// Prüfen ob alle Einstellungen für einen Integer Wert eingehalten wurden
        /// </summary>
        /// <param name="value">Der Wert der überprüft werden soll</param>
        /// <param name="dynamicDataType">Der Dynamische Datentyp der die Regeln beinhaltet</param>
        /// <returns>TRUE->Eingabe richtig | FALSE->Fehler bei der Eingabe</returns>
        private bool CheckDecimal(decimal value, EFDynamicDataType dynamicDataType)
        {
            //Prüfen ob ein Regexeintrag angegeben ist und ob dieser überprüft werden soll auch bei Zahlen kann evtl. ein bestimmtes Format gefordert sein
            if (!string.IsNullOrEmpty(dynamicDataType.RegExString))
            {
                if (!Regex.IsMatch(value.ToString(), dynamicDataType.RegExString))
                {
                    //Wenn es sich um einen Fehler handelt, diesen der Auflistung mit den Eingabefeldfehlern hinzufügen
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,GlobalResources.SystemMessages.WrongValueFormatRESX));
                    return false;
                }
            }

            //Reihenfolge ist wichtig, als erstes prüfen ob beide Werte angegeben werden sollen.
            //Der Integer darf maximal xx Groß sein und minimal xx Klein sein
            if (dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob die Zahl größer ist wie gefordert bzw. kleiner ist wie gefordert
                if (value > dynamicDataType.MaxLength || value < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMinMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der Integer darf maximal xxx groß sein
            if (dynamicDataType.MaxLength.HasValue && !dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value > dynamicDataType.MaxLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMaxLengthRESX, dynamicDataType.Name, dynamicDataType.MaxLength)));
                    return false;
                }
            }

            //Der Integer muss mindestens xx lang sein
            if (!dynamicDataType.MaxLength.HasValue && dynamicDataType.MinLength.HasValue)
            {
                //Prüfen ob der String größer ist wie gefordert.
                if (value < dynamicDataType.MinLength)
                {
                    AddValueToValidationErrorList(new DynamicDataValidationError(value.ToString(), dynamicDataType.ID,
                                                  string.Format(GlobalResources.SystemMessages.IntMinLengthRESX, dynamicDataType.Name, dynamicDataType.MinLength)));
                    return false;
                }
            }

            //Alle Eingaben sind i.o. dann kann True zurück gegeben werden.
            return true;
        }

        /// <summary>
        /// Fügt den übergebenen Wert der Errorliste hinzu und setzt gleichzeitig das passende TempData
        /// </summary>
        /// <param name="dataValidationError">der Validation error der der Liste hinzugefügt werden soll</param>
        private void AddValueToValidationErrorList(DynamicDataValidationError dataValidationError)
        {
            ValidationErrorList.Add(dataValidationError.DynamicDataTypeId, dataValidationError);
            //Setzten der Liste als Eintrag im Tempdata, jedesmal, wenn darauf zugegriffen wird.
            CurrentControllerContext.Controller.TempData[DynamicDataValidationError.TempDataString] = ValidationErrorList;
        }
        #endregion
    }
}